package com.leyou.item.service.impl;

import com.leyou.common.exception.LyException;
import com.leyou.auth.dto.SearchRequest;
import com.leyou.item.clients.ItemClient;
import com.leyou.item.dto.BrandDTO;
import com.leyou.item.dto.CategoryDTO;
import com.leyou.item.pojo.Goods;
import com.leyou.item.repository.GoodsRepository;
import com.leyou.item.service.SearchService;
import com.leyou.starter.elastic.entity.PageInfo;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private GoodsRepository repository;

    @Autowired
    private ItemClient itemClient;

    @Override
    public Mono<List<String>> suggest(String key) {
        return repository.suggestBySingleField("suggestion", key);
    }

    @Override
    public Mono<PageInfo<Goods>> listData(SearchRequest searchRequest) {
        //构造条件对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //添加条件
        QueryBuilder queryBuilder = buildQuery(searchRequest);
        sourceBuilder.query(queryBuilder);

        //页数指定
        int size = searchRequest.getRows();
        int from = (searchRequest.getPage() - 1) * 10;

        sourceBuilder.from(from);
        sourceBuilder.size(size);

        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<am>");
        highlightBuilder.postTags("</am>");
        highlightBuilder.field("title");

        sourceBuilder.highlighter(highlightBuilder);

        return this.repository.queryBySourceBuilderForPageHighlight(sourceBuilder);
    }

    //公共查询条件构造
    private QueryBuilder buildQuery(SearchRequest searchRequest) {

        String key = searchRequest.getKey();

        if (StringUtils.isBlank(key)) {
            throw new LyException(400, "请求参数有误");
        }

        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        //给布尔查询中添加普通查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("title", key).operator(Operator.AND));



        Map<String, String> filters = searchRequest.getFilters();

        //有过滤条件才执行过滤
        if (!CollectionUtils.isEmpty(filters)){

            filters.entrySet().forEach(entry->{
                String eKey = entry.getKey();
                String eValue = entry.getValue();
                if ("品牌".equals(eKey)){
                    eKey = "brandId";
                    boolQueryBuilder.filter(QueryBuilders.termQuery(eKey,eValue));
                }else if ("分类".equals(eKey)){
                    eKey = "categoryId";
                    boolQueryBuilder.filter(QueryBuilders.termQuery(eKey,eValue));
                }
            });
        }
        return boolQueryBuilder;
    }

    @Override
    public Mono<Map<String, List<?>>> listFilter(SearchRequest searchRequest) {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        sourceBuilder.query(buildQuery(searchRequest));

        //添加聚合条件
        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brandId").size(20));
        sourceBuilder.aggregation(AggregationBuilders.terms("categoryAgg").field("categoryId").size(20));

        //添加聚合条件
        sourceBuilder.aggregation(
                AggregationBuilders.nested("specAgg","specs")
                        .subAggregation(AggregationBuilders.terms("nameAgg").field("specs.name")
                                .subAggregation(AggregationBuilders.terms("valueAgg").field("specs.value")))
        );


        //执行查询聚合
        Mono<Aggregations> aggregationsMono = repository.aggregationBySourceBuilder(sourceBuilder);

        //不订阅，不重新封装，直接转换
        return aggregationsMono.map(aggregations -> {

            Map<String, List<?>> resultMap = new LinkedHashMap<>();

            Terms brandAgg = aggregations.get("brandAgg");

            List<Long> brandIds = brandAgg
                    .getBuckets()
                    .stream()
                    .map(bucket -> bucket.getKeyAsNumber())
                    .map(Number::longValue)
                    .collect(Collectors.toList());


            Terms categoryAgg = aggregations.get("categoryAgg");

            List<Long> categoryIds = categoryAgg
                    .getBuckets()
                    .stream()
                    .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber())
                    .map(Number::longValue)
                    .collect(Collectors.toList());

            //分别根据品牌id集合和分类id集合查询对应的对象集合，一定要判断健壮性

            if (!CollectionUtils.isEmpty(categoryIds)) {
                List<CategoryDTO> categoryDTOS = this.itemClient.queryCategoryByIds(categoryIds);

                resultMap.put("分类", categoryDTOS);
            }


            if (!CollectionUtils.isEmpty(brandIds)) {
                List<BrandDTO> brandDTOS = this.itemClient.queryBrandByIds(brandIds);

                resultMap.put("品牌", brandDTOS);
            }


            //根据聚合名称获取nested聚合
            Nested specAgg = aggregations.get("specAgg");

            //根据nameAgg获取子聚合
            Terms nameAgg = specAgg.getAggregations().get("nameAgg");

            nameAgg.getBuckets().forEach(bucket->{
                String key = ((Terms.Bucket) bucket).getKeyAsString();

                //根据valueAgg获取子聚合
                Terms valueAgg = ((Terms.Bucket) bucket).getAggregations().get("valueAgg");

                //把valueAgg中聚合结果取出封装成list
                List<String> valueList = valueAgg.getBuckets().stream().map(bucketValue -> bucketValue.getKeyAsString()).collect(Collectors.toList());

                resultMap.put(key,valueList);
            });

            return resultMap;

        });
    }
}
